package com.tagit.sphinx4;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

import edu.cmu.sphinx.frontend.BaseDataProcessor;
import edu.cmu.sphinx.frontend.Data;
import edu.cmu.sphinx.frontend.DataEndSignal;
import edu.cmu.sphinx.frontend.DataProcessingException;
import edu.cmu.sphinx.frontend.DataStartSignal;
import edu.cmu.sphinx.frontend.DoubleData;
import edu.cmu.sphinx.util.props.PropertyException;
import edu.cmu.sphinx.util.props.PropertySheet;
import edu.cmu.sphinx.util.props.S4Integer;

/**
 * Implementation for reading samples from an external microphone. The external
 * microphone should frequently call {@link #readData} while passing the sampling
 * data as double array. However, many language models use 16bit signed PCM
 * format.
 * 
 * If the external microphone reads data as IEEE floats, simply multiply each
 * sample by 32767 and round it up to the closest integer.
 * 
 * @author Mohamed Ali
 * 
 */
public class ExternalMicrophone extends BaseDataProcessor {
	/** The sampling rate used by the external microphone */
	@S4Integer(defaultValue = 16000)
	public static final String PROP_SAMPLE_RATE = "sampleRate";

	private int sampleRate;
	private int totalSamplesRead;

	private volatile boolean recording;
	
	private volatile boolean utteranceEndReached = true;
	
	private BlockingQueue<Data> audioList;

	/**
	 * @param sampleRate	The sampling rate used by the external microphone 
	 */
	public ExternalMicrophone(int sampleRate) {
		super();
		this.sampleRate = sampleRate;
		
		initialize();
	}

	/**
	 * Default Constructor
	 */
	public ExternalMicrophone() {
		
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void newProperties(PropertySheet ps) throws PropertyException {
		super.newProperties(ps);
		
		sampleRate = ps.getInt(PROP_SAMPLE_RATE);
		
		logger = ps.getLogger();
		
		initialize();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void initialize() {
		super.initialize();
		audioList = new LinkedBlockingDeque<Data>();
	}
	
	/**
	 * Checks if the microphone is in recording mode.
	 * @return true if the microphone is in recording mode.
	 */
	public boolean isRecording() {
		return recording;
	}
	
	/**
	 * Initialises microphone recording.
	 * Adds a {@link DataStartSignal} to the queue.
	 * @return true if microphone starts recording successfully.
	 */
	public synchronized boolean startRecording() {
		if (recording)
			return true;
		recording = true;
		utteranceEndReached = false;
		totalSamplesRead = 0;
		clear();
		audioList.add(new DataStartSignal(sampleRate));
		return true;
	}
	
	/**
	 * Stops microphone recording.
	 * Adds a {@link DataEndSignal} to the queue. 
	 */

	public synchronized void stopRecording() {
		recording = false;
		long duration = (long) ((double) totalSamplesRead / (double) sampleRate * 1000.0);
		audioList.add(new DataEndSignal(duration));
	}
	
	/**
	 * Receives data from the external microphone.
	 * @param data	The audio samples.
	 * @param sampleRate The sampling rate at which the data was collected. 
	 * @param collectTime Time in milliseconds at which the samples were collected.
	 */
	public synchronized void readData(double[] data, 
			int collectTime) {
		if (recording) {
			audioList.add(new DoubleData(data, sampleRate, collectTime,
					totalSamplesRead));
			totalSamplesRead += data.length;
		}
	}
	
	/**
	 * Clears the microphone buffer.
	 */
	public void clear() {
		audioList.clear();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Data getData() throws DataProcessingException {
		getTimer().start();

		Data output = null;

		if (!utteranceEndReached) {
			try {
				output = audioList.take();
			} catch (InterruptedException ie) {
				throw new DataProcessingException(
						"cannot take Data from audioList", ie);
			}
			if (output instanceof DataEndSignal) {
				utteranceEndReached = true;
			}
		}

		getTimer().stop();

		return output;
	}

	public int getSampleRate() {
		return sampleRate;
	}

	public void setSampleRate(int sampleRate) {
		this.sampleRate = sampleRate;
	}
}
